package interpreter;

import interpreter.bytecodes.ByteCode;
import interpreter.bytecodes.DumpByteCode;
import java.util.Stack;

/**
 *
 * @author Kristofer Selbekk
 */
public class VirtualMachine {

    // Instance variables
    private RunTimeStack runStack;
    private int pc; // Program counter / bytecode index
    private Stack returnAddrs;
    private boolean isRunning;
    private Program program;
    private boolean dump;

    /**
     * CONSTRUCTOR:
     *
     * Sets up the virtual machine with a given Program
     * @param p the Program to run
     */
    public VirtualMachine( Program p ) {
        program = p;
        pc = 0;
        runStack = new RunTimeStack();
        returnAddrs = new Stack();
    }

    /**
     * Executes the program
     *
     * Instantiates stacks, program counters and sets the program as "running".
     * Then <code>executeProgram()</code> executes each byte code by itself.
     */
    public void executeProgram() {
        isRunning = true;
        while( isRunning ) {
            int pcBackup = pc;
            ByteCode code = program.getCode(pc);
            code.exec(this);

            // Prints out representation if "dump" is set to true
            if( dump && !( code instanceof DumpByteCode) )
                System.out.println( code +"\n"+ runStack.dump());

            // If the program counter was NOT modified by the byte codes, tick forward
            if( pcBackup == pc )
                pc++;
        }
    }

    /**
     * Pops <em>elems</em> elements off the runtime stack
     *
     * TODO: Check if the pop is feasible
     *
     * @param elems number of elements
     * @return element removed
     */
    public int popStack() {
        return runStack.pop();
    }

    /**
     * Stores the top of the stack at offset
     *
     * Action deferred to the RunTimeStack-object
     *
     * @param offset the offset to save in
     */
    public void store( int offset ) {
        runStack.store(offset);
    }

    /**
     * Sets the dump switch to true or false
     * @param status
     */
    public void setDump( String status ) {
        dump = false;
        if( status.equalsIgnoreCase( "ON" ) )
            dump = true;
        return;
    }

    /**
     * Sets up new frame / activation record
     *
     * Called by the <code>ARGS</code> byte code.
     *
     * @param offset number of arguments for the new frame
     */
    public void setNewFrame( int offset ) {
        runStack.newFrameAt(offset);
    }

    /**
     * Stops the program
     *
     * Called by the <code>HALT</code> byte code
     */
    public void haltProgram() {
        isRunning = false;
    }

    /**
     * Pushes a value onto the top of the runtime stack.
     * 
     * @param val the value to be pushed (or added) to stack
     */
    public void pushOnStack( int val ) {
        runStack.push( val );
    }

    /**
     * Loads the element at a given offset to the top of the stack
     *
     * @param offset the offset from the top of the stack
     */
    public void load( int offset ) {
        runStack.load( offset );
    }

    /**
     * Gets the runtime stack size
     * @return size of runtime stack
     */
    public int getStackSize() {
        return runStack.size();
    }

    public int argCount() {
        return runStack.argCount();
    }

    /**
     * Returns the top of the stack
     * @return top element of stack
     */
    public int getTopOfStack() {
        return runStack.peek();
    }

    public int getStackAt( int offset ) {
        return runStack.getStackElementAt(offset);
    }

    // Setter and getter for program counter
    public int getPC() {
        return pc;
    }

    public void setPC( int pc ) {
        this.pc = pc;
    }

    // Add and pop method for the returnAddr data structure
    public void addReturnAddress( int address ) {
        returnAddrs.push( address );
    }

    public int popReturnAddress() {
        return (Integer) returnAddrs.pop();
    }

    // Removes top frame
    public void popTopFrame() {
        runStack.popFrame();
    }

    // Gets starting point of top frame
    public int getTopFrame() {
        return runStack.getTopFrame();
    }

    // Gets the element at index in runtime stack
    public int getStackElementAt( int index ) {
        return runStack.getStackElementAt( index );
    }

    // Gets whether or not the vm is running
    public boolean isRunning() {
        return isRunning;
    }

    // Gets whether or not the vm is dumping
    public boolean isDump() {
        return dump;
    }

    // Returns the runtime stack
    public String getDump() {
        return runStack.dump();
    }

    // Sets the machine as running
    public void setRunning( boolean b ) {
        isRunning = b;
    }
    
}
